package org.tp;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.awt.*;


// Abstract Syntax Tree
// decorated with attributs 
// tag: node label (cf EnumTag)


public class AbSynt {

	private int id;   // used in toDot
	private AbSynt left;
	private AbSynt right;
	private EnumTag tag;	// node label
	private String str;     // used for identifiers
	private char letter;		// used for letter
	private Type type;

	public AbSynt(AbSynt left, AbSynt right, EnumTag tag, Type t) {
		this.left=left;
		this.right=right;
		this.tag=tag;
		this.str="";
		this.letter=' ';
		this.type=t;
	}
	public AbSynt(AbSynt left, EnumTag tag, Type t) {
		this.left=left;
		this.right=null;

		this.tag=tag;
		this.str="";
		this.letter=' ';
		this.type=t;
	}

	public AbSynt(AbSynt left, AbSynt right, EnumTag tag) {
		this(left, right, tag, null);
	}


	public AbSynt(EnumTag tag) {
		this(null, null, tag, null);
	}

	public AbSynt(EnumTag tag, String str, Type type) {
		this(null, null, tag, type);	
		this.str=str;
	}

	public AbSynt(EnumTag tag, char c, Type type) {
		this(null, null, tag, type);	
		this.letter=c;
	}

	public AbSynt(EnumTag tag, String str) {
		this(null, null, tag, null);	
		this.str=str;
	}

	public AbSynt getLeft() {
		return left;
	}

	public void setLeft(AbSynt left) {
		this.left = left;
	}

	public AbSynt getRight() {
		return right;
	}

	public String getName() {
		return str;
	}
	/** retourne les id des arguments en string separer par des ":"**/
	public String getArgs(){
		String result = new String();
		if (tag != EnumTag.ARG){
			//System.out.println("ARG !=VAR" +result);
			return result;
		}
		if(this.getType(null)!=null)
			if(left==null){
				result+=""+this.getType(null);
			}
		if ((left != null) || (right != null)){
			if (left != null){
				result += left.getArgs();
				//System.out.println("left"+result);
			}
			if (right != null){
				result+=":";
				result += right.getArgs();
				//	System.out.println("right"+result);
			}
		}
		//System.out.println("ARG "+result+"\n");
		return result;
	}
	/** rentre dans la hashmap les noms des variables associe avec leur type 
	 * sert pour les parametres d'une fonction, ajout au variable locale**/
	public void getIdEtTypeArgs(HashMap<String,Variable> HM){

		if(left!=null){
			left.getIdEtTypeArgs(HM);
		}
		if(right!=null){
			right.getIdEtTypeArgs(HM);
		}
		if(this.getType(null)!=null){
			HM.put(this.str,new Variable(this.str,this.type,null));
		}
	}
	/**retourne les type en string des arguments separe par ":" **/
	public String getTypeArgs(){
		String result = new String();
		//if (tag != EnumTag.ARG){
		//System.out.println("ARG !=VAR" +result);
		//return result;
		//}
		if(this.getType(null)!=null)
			if(left==null){
				result+=""+this.getType(null);
			}
		if ((left != null) || (right != null)){
			if (left != null){
				result += left.getTypeArgs();
				//System.out.println("left"+result);
			}
			if (right != null){
				result+=":";
				result += right.getTypeArgs();
				//	System.out.println("right"+result);
			}
		}
		//System.out.println("ARG "+result+"\n");
		return result;
	}	

	/** retourne les variable d'un arbre chacune separer par un "." **/
	public String getID(){
		String result = new String();
		if (tag != EnumTag.VAR){
			//System.out.println("tag !=VAR" +result);
			if(tag==EnumTag.TAB){
				result=left.getID();
			}
			return result;
		}
		if(this.getName()!=null)
			if(left==null){
				result+=""+this.getName();
			}
		if ((left != null) || (right != null)){
			if (left != null){
				result += left.getID();
				//System.out.println("left"+result);
			}
			if (right != null){
				result+=".";
				result += right.getID();
				//	System.out.println("right"+result);
			}
		}
		//System.out.println("GETID RETOUR "+result+"\n");
		return result;
	}

	public void setRight(AbSynt right) {
		this.right = right;
	}
	/** retourne le type de retour d'une operation en fonction 
	 * du membre droit ou gauche produit une erreur sinon**/
	public Type assoc(String op,Type left,Type right){
		if(left ==null||right==null){
			if(left==null)
				return right;
			else
				return left;
		}
		else{
			switch (left.getEnumType()){
			case CHAR :
				if(!op.equals("bool")){
					if(op.equals('+')){
						switch (right.getEnumType()){
						case INT: //System.out.println("CHAR+INT");
							return new Type(EnumType.CHAR);

						case CHAR: //System.out.println("CHAR+INT");
							return new Type(EnumType.CHAR); 

						default:
							Toolkit.getDefaultToolkit().beep(); 
							System.out.println("ERREUR ASSOC RIGHT");
							//System.exit(1);
							break;
						}
					}
					else{
						Toolkit.getDefaultToolkit().beep();

						System.out.println("ERREUR OPERATEUR TYPE");
						System.exit(1);
						break;
					}
				}
			case STRING :
				if(!op.equals("bool")){
					if(op.equals('+')){
						switch (right.getEnumType()){
						case INT: //System.out.println("CHAR+INT");
							return new Type(EnumType.STRING);

						case CHAR: //System.out.println("CHAR+INT");
							return new Type(EnumType.STRING); 

						default:
							Toolkit.getDefaultToolkit().beep(); 
							System.out.println("ERREUR ASSOC RIGHT");
							System.exit(1);break;
						}
					}
					else{
						Toolkit.getDefaultToolkit().beep(); 
						System.out.println("ERREUR OPERATEUR TYPE");
						System.exit(1);break;
					}
				}
			case INT :
				if(!op.equals("bool")){
					switch (right.getEnumType()){
					case INT:// System.out.println("INT+INT");
						return new Type(EnumType.INT); 
					case FLOAT://System.out.println("FLOAT+INT"); 
						return new Type(EnumType.FLOAT);
					default:
						Toolkit.getDefaultToolkit().beep(); 
						System.out.println("ERREUR ASSOC RIGHT");
						System.exit(1);break;
					}
				}
			case FLOAT :
				if(!op.equals("bool")){

					switch (right.getEnumType()){
					case INT://System.out.println("INT+FLOAT"); 
						return new Type(EnumType.FLOAT); 
					case FLOAT: //System.out.println("FLOAT+FLOAT");
						return new Type(EnumType.FLOAT); 
					default:
						Toolkit.getDefaultToolkit().beep(); 
						System.out.println("ERREUR ASSOC RIGHT");
						System.exit(1);break;
					}
				}
			case BOOLEAN:
				if(op.equals("bool")){
					if(right.getEnumType()==EnumType.BOOLEAN){
						return new Type(EnumType.BOOLEAN);
					}
					else{
						Toolkit.getDefaultToolkit().beep(); 
						System.out.println("right not BOOLEAN");
						System.exit(1);break;
					}
				}
				else{
					Toolkit.getDefaultToolkit().beep(); 
					System.out.println("ERREUR BOOLEAN AND OR");
					System.exit(1);break;	
				}

			default:
				Toolkit.getDefaultToolkit().beep(); 
				System.out.println("WARNING MEMBRE DROIT TYPE NON TRAITER ");
				break;

			}
		}
		return null;
	}
	/**indique si deux types sont comparables**/
	public Type Comparable(Type left,Type right){
		//System.out.println(right.getEnumType().toString());
		if(right.getEnumType().toString()=="NULL"){
			return new Type(EnumType.BOOLEAN);

		}
		//System.out.println("COMPARE "+left.toString()+" "+right.toString());
		switch(left.getEnumType()){
		case INT:
			switch(right.getEnumType()){
			case FLOAT:
			case INT:
				//System.out.println("COMPARE OKEY");
				return new Type(EnumType.BOOLEAN);
			default:
				Toolkit.getDefaultToolkit().beep(); 
				System.out.println("TYPE NON COMPARABE");
				System.exit(1);
			}
		case FLOAT:
			switch(right.getEnumType()){
			case FLOAT:
			case INT:
				//System.out.println("COMPARE");
				return new Type(EnumType.BOOLEAN);
			default:
				Toolkit.getDefaultToolkit().beep(); 
				System.out.println("TYPE NON COMPARABE");
				System.exit(1);
			}
		case STRING:
			switch(right.getEnumType()){
			case STRING:
				//System.out.println("COMPARE");
				return new Type(EnumType.BOOLEAN);
			default:
				Toolkit.getDefaultToolkit().beep(); 
				System.out.println("TYPE NON COMPARABE");
				System.exit(1);
			}

		case BOOLEAN:
			if( right.getEnumType().equals(EnumType.BOOLEAN)){
				//System.out.println("COMPARE");
				return new Type(EnumType.BOOLEAN);
			}
			else {
				Toolkit.getDefaultToolkit().beep(); 
				System.out.println("TYPE NON COMPARABE");
				System.exit(1);
			}				
		default:
			Toolkit.getDefaultToolkit().beep(); 
			System.out.println("TYPE NON COMPARABE");
			System.exit(1);
		}
		Toolkit.getDefaultToolkit().beep(); 
		System.out.println("TYPE NON COMPARABE");
		System.exit(1);
		return null;
	}
	public Type getType(){
		return type;
	}
	/** renvoie le type en fonction des operations **/
	public Type getType(Type ret) { 
		Type type =this.type;
		switch(tag){
		case NO :
			if(this.getLeft().getType(new Type(EnumType.BOOLEAN)).getEnumType().toString()=="boolean"){
				return new Type(EnumType.BOOLEAN);
			}
			System.out.println("erreur ! expr, expression not bool");
			System.exit(1);
			break;
		case TAB:
			StringTokenizer tab = new StringTokenizer(str, "["); 
			String tabID=tab.nextToken();
			type=Main.current.getTypeOf(tabID);
			if(type==null){
				if((type=Main.varGlob.getVar(tabID).getType())==null){
					System.out.println("LE TABLEAU"+tabID+"N'EXISTE PAS");
					System.exit(1);
				}
			}
			break;
		case MAP:
			System.out.println("WARNING MAP : pas de verification");
			type=ret;
			break;
		case SET:
			System.out.println("WARNING SET ou LIST : pas de verification");
			type=ret;
			break;
		case VAR:
			//Test pour une fonction aussi!
			str=getID();
			//System.out.println("STR :"+str);
			StringTokenizer st = new StringTokenizer(str, "."); 
			if(st.countTokens()>1){ // CAS D'UNE STRUCTURE
				/*______________________STRUCTURE_________________________________*/
				type=VerifType.getTypeStruct(st);

				if(type==null){
					Toolkit.getDefaultToolkit().beep(); 
					System.out.println(id+" NOT DECLARED");
					System.exit(1);	
				}
				else{
					break;
				}
			}
			else{
				if((type=Main.current.getTypeOf(str))!=null){
					break;
				}

				//System.out.println(tag.toString()+" : "+str+"\n");
				if(Main.varGlob.getVar(str)!=null){
					type=Main.varGlob.getVar(str).getType();
				}
				else{
					Toolkit.getDefaultToolkit().beep(); 
					System.out.println(id+" NOT DECLARED");
					System.exit(1);
				}
			}
			break;
		case MULT:
			//System.out.println(tag.toString());
			type=assoc("*",left.getType(null), right.getType(null));
			break; /**/
		case MOD:
			//System.out.println(tag.toString());
			type=assoc("%",left.getType(null), right.getType(null));
			break; /**/
		case DIV :
			/**/
			//System.out.println(tag.toString());
			type=assoc("/",left.getType(null), right.getType(null));
			break;
		case PLUS:
			/**/
			//System.out.println("PLUS"+tag.toString()+"\n");
			type=assoc("+", left.getType(null), right.getType(null));
			//System.out.println(type);
			break;
		case MINUS:
			//System.out.println(tag.toString());
			type=assoc("-", left.getType(null), right.getType(null));
			/*association left-right*/
			/*float*/
			break;
		case MINUS_U:
			//System.out.println(tag.toString());
			type=ret;
			break;
		case LT :
		case LE:
		case GE:
		case GT:
		case DIFF:
		case EQ :
			//System.out.println("COMPARE");
			type=Comparable(left.getType(null),right.getType(null));
			break;
		case AND :
		case OR :
			type=assoc("bool", left.getType(null), right.getType(null));
			/*FAUX, VERIFIER TYPE COMPATIBLE /COMPARABLE*/
			//System.out.println(tag.toString());
			type=new Type(EnumType.BOOLEAN);
			break;
		case APFCT:
			if(Main.fonctions.getFonction(left.str,ret.toString(),right)!=null){
				//type=new Type(EnumType.INT);
				//System.out.println("okey");
				type= ret;
			}
			else{
				if(left.getRight()!=null){
					System.out.println("WARNING : pas de verification pour "
							+left.getLeft().getName()+"."
							+left.getRight().getName()
							);
				}
				else{
					System.out.println("WARNING : pas de verification pour "
							+left.getName()
							);
				}
				//System.exit(1);
			}
			type=ret;
			break;
		default:
			//System.out.println("DEFAULT :"+tag.toString()+this.type+"\n");
			type=this.type;
			;
		}
		if(type==null){
			System.out.println("WARNING TYPE NON TRAITE");
		}
		return type;
	}

	public EnumTag getTag() {
		return tag;
	}    



	public String toString() {

		String result = new String();
		result += tag.toString();
		if(this.getName()!=null)
			result+=" "+this.getName();
		if ((left != null) || (right != null)){
			result +="(";
			if (left != null)
				result += left.toString();
			if (right != null){
				result+=",";
				if (right.tag==EnumTag.SUCC || right.tag==EnumTag.THENELSE)
					result+="\n\n\t";
				result += right.toString();
			}
			result+=")";
		}
		//System.out.println(result);
		return result;
	}

	public void toDot(StringBuffer str) {
		//System.out.println("toDOT"+tag.toString());
		str.append(this.id+" [label=\""+tag.toString());
		switch (tag){
		case INTEGER: 
		case STRING:
		case VAR:      
			str.append(": "+this.str); 
			break;
		}
		if(tag == EnumTag.SET || tag == EnumTag.MAP){
			//System.out.println("SET OU MAP");

			str.append("\"];\n");

		}
		else{
			if (type!=null){
				str.append(" (");
				str.append(type.toString());
				str.append(")");
			}
			str.append("\"];\n");
			if (left != null){
				left.toDot(str);
				str.append(this.id+" -> "+left.id+";\n");
			}
			if (right != null){
				right.toDot(str);
				str.append(this.id+" -> "+right.id+";\n");
			}


		}
	}

	public void toDot(String file) {
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter("./"+file+".dot"));
			out.write("digraph ArbreSyntAbstr {");
			StringBuffer str = new StringBuffer();
			toDot(str);
			out.write(str.toString());
			out.write("}");
			out.close();
		} catch (IOException e) {
			Toolkit.getDefaultToolkit().beep(); 
			System.out.println("ERROR: build dot");
		}		
	}
	/** lance le parcours de l'arbre**/
	public void ecrireAbSynt() throws IOException
	{	
		BufferedWriter out = new BufferedWriter(new FileWriter("./Main.java"));
		out.write("import java.util.* ;\n");
		out.write("public class Main {\n\n");
		out.flush();
		Parcours p = new Parcours(out, this);
		p.parcours();
		out.write("\n}");
		out.flush();
		out.close();
	}
}
